﻿using ICSharpCode.SharpZipLib.Core;
using ICSharpCode.SharpZipLib.Zip;
using System;
using System.Collections.Generic;
using System.IO;

namespace Engine.Helpers
{
    public class ZipArchive
    {
        #region Private Fields

        private int compressLevel = 6;
        private Dictionary<string, MemoryStream> files;

        #endregion Private Fields

        #region Public Constructors

        public ZipArchive()
        {
            this.files = new Dictionary<string, MemoryStream>();
        }

        #endregion Public Constructors

        #region Public Methods

        public void SaveToFile(string filename)
        {
            byte[] buffer = new byte[4096];

            FileStream fsZip = File.Create(filename);
            ZipOutputStream zipStream = new ZipOutputStream(fsZip);
            zipStream.UseZip64 = UseZip64.Off;
            zipStream.SetLevel(this.compressLevel);

            foreach (var item in this.files)
            {
                item.Value.Position = 0;
                ZipEntry entry = new ZipEntry(item.Key);
                // To permit the zip to be unpacked by built-in extractor in WinXP and Server2003, WinZip 8, Java, and other older code,
                // you need to do one of the following: Specify UseZip64.Off, or set the Size.
                // If the file may be bigger than 4GB, or you do not need WinXP built-in compatibility, you do not need either,
                // but the zip will be in Zip64 format which not all utilities can understand.
                //   zipStream.UseZip64 = UseZip64.Off;
                entry.Size = item.Value.Length;

                zipStream.PutNextEntry(entry);

                StreamUtils.Copy(item.Value, zipStream, buffer);

                zipStream.CloseEntry();
            }

            zipStream.Close();
            fsZip.Close();
        }

        #endregion Public Methods

        #region Internal Methods

        public static ZipArchive FromFile(string filename)
        {
            ZipArchive zipArchive = new ZipArchive();

            FileStream fs = new FileStream(filename, FileMode.Open);
            ZipFile zf = new ZipFile(fs);

            foreach (ZipEntry zipEntry in zf)
            {
                if (!zipEntry.IsFile)
                {
                    continue;           // Ignore directories
                }
                String entryFileName = zipEntry.Name;
                // to remove the folder from the entry:- entryFileName = Path.GetFileName(entryFileName);
                // Optionally match entrynames against a selection list here to skip as desired.
                // The unpacked length is available in the zipEntry.Size property.

                byte[] buffer = new byte[4096];     // 4K is optimum
                Stream zipStream = zf.GetInputStream(zipEntry);

                // Unzip file in buffered chunks. This is just as fast as unpacking to a buffer the full size
                // of the file, but does not waste memory.
                // The "using" will close the stream even if an exception occurs.
                using (MemoryStream streamWriter = new MemoryStream())
                {
                    StreamUtils.Copy(zipStream, streamWriter, buffer);
                    streamWriter.Position = 0;
                    streamWriter.Flush();
                    zipArchive.AddData(zipEntry.Name, streamWriter, true);
                }
            }
            fs.Close();
            zf.Close();

            return zipArchive;
        }

        public void AddData(string fileName, Stream bw, bool replaceCurrentFile)
        {
            byte[] buffer = new byte[4096];
            fileName = fileName.ToUpper();
            MemoryStream ms;
            if (this.files.TryGetValue(fileName, out ms))
            {
                StreamUtils.Copy(bw, ms, buffer);
            }
            else
            {
                ms = new MemoryStream();
                StreamUtils.Copy(bw, ms, buffer);
                ms.Flush();
                this.files.Add(fileName, ms);
            }
        }

        public bool ContainsFile(string filename)
        {
            filename = filename.ToUpper();

            return this.files.ContainsKey(filename);
        }

        public List<string> GetFiles()
        {
            List<string> filenames = new List<string>();

            foreach (var item in this.files.Keys)
            {
                filenames.Add(item);
            }

            return filenames;
        }

        public List<string> GetFiles(string filter)
        {
            filter = filter.ToUpper();
            List<string> filenames = new List<string>();

            foreach (var item in this.files.Keys)
            {
                if (item.Contains(filter))
                    filenames.Add(item);
            }

            return filenames;
        }

        public Stream GetStream(string filename)
        {
            filename = filename.ToUpper();
            MemoryStream ms;
            if (this.files.TryGetValue(filename, out ms))
            {
                ms.Position = 0;
                return ms;
            }
            throw new Exception("File not found in ZipArchive");
        }

        #endregion Internal Methods
    }
}